toolbag3 shader

Created by miccall (转载请注明出处 miccall.tech)

一 . 渲染流程 :

每一层的Shader都维护一个结构体,然后使用inout的形式进行计算,并传入下一流程

首先 Vertex shader 他会创建一个结构体 VertexState 处理 MSET_TESSELLATION 和 Merge 操作

Hull shader 创建 ControlState和PatchState 结构体

Domain shader 创建 ControlState PatchState DomainState 处理 Subdivision 和 Displacement 操作

Geometry shader 创建 GeometryState

Fragment shader 创建 FragmentState ,并处理很多操作,我们的研究也是在这里 ,以上作为了解,不多深究

二. Material 计算步骤

开始向 fragment shader 传入数据 mat/mat.frag :

BEGIN_PARAMS   

    INPUT0(vec3,fPosition)
    INPUT1(vec4,fColor)
    INPUT2(vec3,fTangent)
    INPUT3(vec3,fBitangent)
    INPUT4(vec3,fNormal)
    INPUT5(vec4,fTexCoord)
。。。 
END_PARAMS

进行相关初始化计算 ,放入 FragmentState 结构体 ,方便以后使用 FragmentState 在 state.frag 中

然后简单看一下结构体里面的相关类型(复制的,忽略大小写 )


VEC3    vertexPosition    在3D空间中的位置。
VEC3    vertexEye    从位置到相机的单位矢量。
flaot    vertexEyeDistance    从位置到相机的直接距离。
VEC 2    vertexTexCoord    网格纹理坐标。
VEC 2    vertexTexCoordSecondary    二次网格纹理坐标; 经常缺席/未使用。
vec4    vertexColor    网眼颜色; 如果没有,将是白色的。
VEC3    vertexNormal    网格法向量。
VEC3    vertexTangent    网格切线矢量。
VEC3    vertexBitangent    网格bitangent矢量。
VEC 2    screenTexCoord    坐标为[0,1]中的屏幕位置。可以使用此样本对全屏纹理进行采样。
浮动    screenDepth    投影后深度值。与vertexPosition.z不同。
UINT    sampleCoverage    用于样本覆盖的位掩码; 通常仅用于体素传递。
INT    instanceID    实例编号; 通常仅用于体素传递。
vec4    albedo    rgb中的反照率或漫反射颜色,不透明度可选地为alpha。
VEC3    normal    表面法线方向作为单位矢量。
浮动    gloss    表面粗糙度为[0,1]上的标量。
VEC3    reflectivity    彩色镜面反射率。
VEC3    fresnel    菲涅耳强度。
VEC3    diffuseLight    所有漫射照明的总和。
VEC3    specularLight    所有高光照明的总和。
VEC3    emissiveLight    所有自发光照明的总和。
vec4    generic0 ... 3    一些子程序特殊使用的四个通用值。
vec4    diffuseGI    RGB中的全局漫反射照明,A中的掩模。并不总是存在。
vec4    specularGI    RGB中的全局镜面反射,A中的掩模。并不总是存在。
vec4    OUTPUT0 ... 7    最终渲染颜色输出。通常只在Merge子例程中修改。

然后就是参数计算,先简单看一下,后面会有源码


Premerge
Surface
Microsurface
Albedo
Reflectivity
Lighting or  {  Diffusion , Reflection , Occlusion , Cavity }
Emissive
Transparency
Merge
USE_OUTPUT1 2 3 4 5 6 7

三. 灯光 Lighting 项比较特殊

在 Light.frag
的 MaterialLighting( inout FragmentState s ) 方法 :
步骤:


    DiffusionEnv
    ReflectionEnv
    ReflectionSecondaryEnv
    Occlusion

    循环  uLightCount  ,记录  LightParams 参数 
    other/lightParams.frag
    并计算 
        Diffusion
        Reflection
        ReflectionSecondary
        如果体素渲染要采样 
            sampleShadowMap
        否则
            sampleShadowMask

        写入结构体参数:
        struct  LightParams
        {
            vec3    color;          // "colour"
            vec3    toSource;       // vector from shaded point to light
            vec3    direction;      // normalized vector to light
            float   invDistance;    // 1/distance to light
            float   distance;       // distance to light
            float   attenuation;    // dimming (distance and other factors)
            vec3    attenParams;    // attenuation parameters
            vec2    spotParams;     // spotlight parameters
            vec3    size;           // width, height, thickness
            vec3    axisX, axisY;   // 2D area axes
            float   id;             // light index
            vec4    shadow;         // shadow fraction
        };

    Cavity

四.一些函数列表


// Premerge
    DirectLightPremerge
    VoxelizationPremerge

// Surface
    SurfaceNormalMap
    SurfaceParallaxMap
    SurfaceDetailNormalMap

// Microsurface
    MicrosurfaceGlossMap

// Albedo
    AlbedoMap
    AlbedoVertex
    AlbedoDota

//  Reflectivity
    ReflectivityDota
    ReflectivityMetalness
    ReflectivityRefractiveIndex
    ReflectivitySpecularMap

// Lighting
    MaterialLighting

// Emissive
    EmissiveFluorescentImage
    EmssiveHeat
    EmssiveMap
    DiffusionShadowCatcherEmissive

// Transparency
    TransparencyAlphaTest
    TransparencyBlend
    TransparencyDither
    PrepassRefraction
    TransparencyRefraction
    VoxelAlphaTest
    TransparencyRefractionSpecular

// Merge
    DirectLightMerge
    ExportMerge
    LightMerge
    PrepassMerge
    ScatterMerge
    ShadowMapMerge
    VoxelizationMerge
    VoxelizationMerge
    VoxelizationMerge
    WireframeMerge


// Diffusion
    DiffusionMicrofiberExport
    DiffusionScatterExport
    DiffusionDotaLight
    DiffusionLambertianLight
    DiffusionMicrofiberLight
    DiffusionShadowCatcherLight
    PrepassShadowCatcher
    PrepassSubsurface
    DiffusionScatterLight
    VoxelizeShadowCatcher

// Reflection
    ReflectionAniso
    ReflectionGGX
    ReflectionMirror
    ReflectionAnisotropicLight1
    ReflectionGGXLight1
    MirrorLight1
    BlinnPhongLight1
    BlinnPhongLight1

五. 源码

下面的部分就是直接抄的了 :
把猴的界面参数:

我们也是按照这个控制,来进行分析:

Subdivision有两个选项 ,第一个是 falt 在 HULL着色器阶段

void  PatchFlat( inout ControlState s, inout PatchState ps )
{
    ps.edgeTessellation[0] = max( length( s.position[1] - s.position[2] ) * uTessellationFactor, 1.0 );
    ps.edgeTessellation[1] = max( length( s.position[2] - s.position[0] ) * uTessellationFactor, 1.0 );
    ps.edgeTessellation[2] = max( length( s.position[0] - s.position[1] ) * uTessellationFactor, 1.0 );
    ps.centerTessellation = max( max( ps.edgeTessellation[0], ps.edgeTessellation[1]), ps.edgeTessellation[2] );
}

第二个是 PN Triangles 在 domain 阶段 ,处理 Subdivision

void SubdivisionPN( inout ControlState s, inout PatchState ps, inout DomainState ds )
{
    vec3 b030 = s.position[0].xyz,
         b003 = s.position[1].xyz,
         b300 = s.position[2].xyz;
    vec3 b201 = ps.constants[0];
    vec3 b210 = ps.constants[1];
    vec3 b102 = ps.constants[2];
    vec3 b111 = ps.constants[3];
    vec3 b120 = ps.constants[4];
    vec3 b012 = ps.constants[5];
    vec3 b021 = ps.constants[6];
    float   u = s.domainCoord.x,
            v = s.domainCoord.y,
            w = s.domainCoord.z;
    float   u2 = u*u,   v2 = v*v,   w2 = w*w;
    float   u3 = u2*u,  v3 = v2*v,  w3 = w2*w;
    ds.position =   b300*w3 + b030*u3 + b003*v3 +
                    3.0 * ( b210 * (w2 * u) +
                            b120 * (w * u2) +
                            b201 * (w2 * v) +
                            b021 * (u2 * v) +
                            b102 * (w * v2) +
                            b012 * (u * v2) ) +
                    b111 * ((6.0 * w) * (u * v));
}

displacement 在 domain 阶段
第一个选项是vector

    void DisplacementVector( inout ControlState s, inout PatchState ps, inout DomainState ds )
    {
        vec3 disp = texture2DLod( tDisplacementVectorMap, ds.texcoord.xy, 0.0 ).xyz;
        HINT_FLATTEN
        if( uDisplacementTangentSpace > 0.0 )
        {
            disp =  disp.x * normalize(ds.tangent) +
                    disp.y * normalize(ds.bitangent) +
                    disp.z * normalize(ds.normal);
        }
        ds.position += uDisplacementScale*disp + uDisplacementBias;
    }

第二个选项是 Height

    void  DisplacementHeight( inout ControlState s, inout PatchState ps, inout DomainState ds )
    {
        float disp = texture2DLod( tDisplacementHeightMap, ds.texcoord.xy, 0.0 ).x;
        disp = disp*uDisplacementScaleBias.x + uDisplacementScaleBias.y;
        ds.position += disp * ds.normal;
    }

Surface 之后的都在 fragment shader
SurfaceNormalMap


    void  SurfaceNormalMap( inout FragmentState s )
    {
        //sample and scale/bias the normal map
        vec4 nsamp = textureMaterial( tNormalMap, s.vertexTexCoord );
        vec3 n = uNormalMapScale*nsamp.xyz + uNormalMapBias;

        //ortho-normalization
        vec3 T = s.vertexTangent;
        vec3 B = s.vertexBitangent;
        vec3 N = s.vertexNormal;
        float renormalize = uNormalMapParams.x, orthogonalize = uNormalMapParams.y;
        N = mix( N, normalize(N), renormalize );
        T -= (orthogonalize * dot(T,N)) * N;
        T = mix( T, normalize(T), renormalize );
        vec3 orthB = orthogonalize * (dot(B,N)*N + dot(B,T)*T);
            // don't subtract if it results in 0, which can't be normalized:
            float valueNonZero = float(any(greaterThan( abs(B - orthB), vec3(0.0,0.0,0.0) )));
            B -= orthB * valueNonZero;
        B = mix( B, normalize(B), renormalize );
        //regenerate bitangent
        vec3 B2 = cross( N, T );
        B2 = dot(B2,B) < 0.0 ? -B2 : B2;
        B = mix( B, B2, uNormalMapParams.z );

        // object/tangent space switch
        HINT_FLATTEN
        if( uNormalMapObjectSpace > 0 )
        { n = mulVec( uNormalMapObjectSpaceMatrix, n ); }
        else
        { n = n.x*T + n.y*B + n.z*N; }
        //store our results
        s.normal = normalize( n );
        s.vertexTangent = T;
        s.vertexBitangent = B;
        s.vertexNormal = N;
        s.albedo.a = nsamp.a;
    }

SurfaceParallaxMap


    float ParallaxSample( vec2 c )
    {
        return 1.0 - dot( texture2DLod( tParallaxHeightMap, c, 0.0 ), uParallaxSwizzle );
    }
    void    SurfaceParallaxMap( inout FragmentState s )
    {
        vec3 dir =  vec3(   dot( -s.vertexEye, s.vertexTangent ),
                            dot( -s.vertexEye, s.vertexBitangent ) * uParallaxFlipY,
                            dot( -s.vertexEye, s.vertexNormal ) );
        vec2 maxOffset = dir.xy * (uParallaxDepthOffset.x / (abs(dir.z) + 0.001));

        float minSamples = 16.0;
        float maxSamples = 128.0;
        float samples = saturate( 3.0*length(maxOffset) );
        float incr = rcp( mix( minSamples, maxSamples, samples ) );
        vec2 tc0 = s.vertexTexCoord - uParallaxDepthOffset.y*maxOffset;
        float h0 = ParallaxSample( tc0 );
        HINT_LOOP
        for( float i=incr; i<=1.0; i+=incr )
        {
            vec2 tc = tc0 + maxOffset * i;
            float h1 = ParallaxSample( tc );
            if( i >= h1 )
            {
                //hit! now interpolate
                float r1 = i, r0 = i-incr;
                float t = (h0-r0)/((h0-r0)+(-h1+r1));
                float r = (r0-t*r0) + t*r1;
                s.vertexTexCoord = tc0 + r*maxOffset;
                break;
            }
            else
            {
                s.vertexTexCoord = tc0 + maxOffset;
            }
            h0 = h1;
        }
        //standard normal mapping
    SurfaceNormalMap(s);
    }

SurfaceDetailNormalMap


    void  SurfaceDetailNormalMap( inout FragmentState s )
    {
        SurfaceNormalMap(s);

        //look up detail normal
        vec2 uv = lerp( s.vertexTexCoord.xy, s.vertexTexCoordSecondary.xy, uDetailUseSecondaryUV );
        uv = uv*uDetailTiling.xy + uDetailTiling.zw;
        vec3 dn = textureMaterial( tDetailNormalMap, uv ).xyz;
        dn = uDetailNormalMapScale*dn + uDetailNormalMapBias;
        //ortho-normalization of new tangent basis
        vec3 T = s.vertexTangent;
        vec3 B = s.vertexBitangent;
        vec3 N = s.normal;
        T -= dot(T,N)*N;
        T = normalize(T);
        B -= dot(B,N)*N + dot(B,T)*T;
        B = normalize(B);

        //blend in the detail normal
        dn =    dn.x * T +
                dn.y * B +
                dn.z * N;
        float detailWeight = dot( textureMaterial( tDetailWeightMap, s.vertexTexCoord ), uDetailWeightSwizzle );
        detailWeight *= uDetailWeight;
        s.normal = normalize( s.normal + dn * detailWeight );
    }

Microsurface
gloss

    void MicrosurfaceGlossMap( inout FragmentState s )
    {
        float g = dot( textureMaterial( tGlossMap, s.vertexTexCoord ), uGlossSwizzle );
        s.gloss = uGlossScaleBias.x * g + uGlossScaleBias.y;
        float h = saturate( dot( s.normal, s.vertexEye ) );
        h = uGlossHorizonSmooth - h * uGlossHorizonSmooth;
        s.gloss = mix( s.gloss, 1.0, h*h );
    }

Albedo
AlbedoMap

    void  AlbedoMap( inout FragmentState s )
    {
        s.albedo = textureMaterial( tAlbedoMap, s.vertexTexCoord );
        s.albedo.xyz *= uAlbedoMapColor;
    }

AlbedoVertex

    void AlbedoVertex( inout FragmentState s )
    {
        AlbedoMap(s);
        vec4 vc = s.vertexColor;
        //sRGB conversion
        vec3 srgb = (vc.xyz*vc.xyz)*(vc.xyz*vec3(0.2848,0.2848,0.2848) + vec3(0.7152,0.7152,0.7152));
        vc.xyz = mix( vc.xyz, srgb, uAlbedoVertexColor.z );
        //color & alpha enables
        s.albedo = mix( s.albedo, s.albedo * vc, uAlbedoVertexColor.xxxy );
    }

AlbedoDota

    void    AlbedoDota( inout FragmentState s )
    {
        //dota mask 1
        vec4 mask1;
        mask1.x = dot( textureMaterial( tDotaDetailMask, s.vertexTexCoord ), uDotaDetailChannel );
        mask1.y = dot( textureMaterial( tDotaDiffuseFresnelMask, s.vertexTexCoord ), uDotaDiffuseFresnelChannel );
        mask1.z = dot( textureMaterial( tDotaMetalnessMask, s.vertexTexCoord ), uDotaMetalnessChannel );
        mask1.w = dot( textureMaterial( tDotaSelfIllumMask, s.vertexTexCoord ), uDotaSelfIllumChannel );
        //fresnel gradients
        float NdotV = saturate( dot( s.normal, s.vertexEye ) );
        vec3 fresnel = texture2D( tDotaFresnelWarp, vec2(NdotV,0.0) ).xyz;

        //albedo
        s.albedo = textureMaterial( tDotaColorMap, s.vertexTexCoord );

        //edge color warp
        vec3 warpedAlbedo = texture3D( tDotaEdgeColorWarp, s.albedo.xyz ).xyz;
        s.albedo.xyz = mix( s.albedo.xyz, warpedAlbedo, fresnel.g * mask1.g );
        //detail map (additive only for now, could do other blend modes)
        s.albedo.xyz += (uDotaDetail * mask1.r) * textureMaterial( tDotaDetailMap, s.vertexTexCoord * uDotaDetailTile ).xyz;
        //self illumination
        s.emissiveLight += (uDotaSelfIllumination * mask1.a) * s.albedo.xyz;

        //metalness reduces albedo
        s.albedo.xyz = s.albedo.xyz - s.albedo.xyz * mask1.b;
        //other shading stages will need these values
        s.generic0 = vec4(  mask1.g,        //diffuse gradient selector
                            mask1.b,        //metalness
                            fresnel.r,      //rim light fresnel
                            fresnel.b   );  //specular fresnel
    }

Diffusion

DotaLight


    void    DiffusionDotaLight( inout FragmentState s, LightParams l )
    {
        adjustAreaLightDiffuse( l, s.vertexPosition );

        //dota scales illumination by a user gradient (default is half-lambert)
        float NdotL = dot( l.direction, s.normal );
        vec3 illum = (1.0/3.1415926) * texture2D( tDotaDiffussionGradient, vec2( 0.5*NdotL + 0.5, s.generic0.x ) ).xyz;
        s.diffuseLight +=   l.attenuation *
                            (illum * l.shadow.rgb) *
                            (s.albedo.xyz * l.color);
    }

lambert

    void    DiffusionLambertianLight( inout FragmentState s, LightParams l )
    {
        adjustAreaLightDiffuse( l, s.vertexPosition );
        float lambert = saturate( (1.0/3.1415926) * dot(s.normal, l.direction) );
        s.diffuseLight +=   (lambert * l.attenuation) *
                            (l.color * l.shadow.rgb) *
                            s.albedo.xyz;
    }

Microfiber

    void    DiffusionMicrofiberLight( inout FragmentState s, LightParams l )
    {
        adjustAreaLightDiffuse( l, s.vertexPosition );
        float NdotL = dot(s.normal, l.direction);
        float lambert = (1.0/3.1415926) * saturate( NdotL );

        //PEACH-FUZZ
        vec4 fuzzMap = texture2D( tMicrofiberMap, s.vertexTexCoord );

        float eyeDP = dot( s.vertexEye, s.normal ); 
        float wrapOcc = 0.5*uMicrofiberScatter;
        wrapOcc = wrapLight(NdotL, wrapOcc) * wrapLightIntegral(wrapOcc);
        vec3 fuzzLight = diffuseFresnel3( eyeDP, uMicrofiberScatter, l.shadow.rgb, uMicrofiberOcc );
        fuzzLight *= wrapOcc;
        fuzzLight *= uMicrofiberColor * fuzzMap.rgb;
        //occlude by high gloss values, wet cloth is not fuzzy!
        float wet = saturate(1.0 - uMaskWithGloss * s.gloss);
        fuzzLight *= wet*wet;
        s.diffuseLight += (lambert * s.albedo.xyz + fuzzLight) * l.shadow.rgb * l.attenuation * l.color;
    }

subsurface Scatter

    void    DiffusionScatterLight( inout FragmentState s, LightParams l, inout DiffusionParams scatter )
    {
        adjustAreaLightDiffuse( l, s.vertexPosition );  
        float DP = dot(l.direction, s.normal);
        float lambert = saturate( (1.0/3.1415926) * DP );
        //TRANSLUCENCY
        float spread = scatter.translucencyScatter * 0.5;
        float wrap = wrapLightSquared(-DP, spread) * wrapLightSquaredIntegral(spread);
        //3x sets translucency color as a mid-point of the ramp
        //0.15 is the bias from pure white at ramp^0
        float ex = (-3.0 * l.shadow.a) + 3.15;
        vec3 ramp = (0.9975 * scatter.translucencyColor) + vec3(0.0025,0.0025,0.0025);
        ramp = pow(ramp*l.shadow.a, ex);

        s.diffuseLight += 
            (l.attenuation * l.color) *
            (lambert * l.shadow.rgb + wrap * ramp * scatter.translucencyMask);
    }

Reflectivity
Dota

    void    ReflectivityDota( inout FragmentState s )
    {
        //pass through values
        #ifdef AlbedoDota_Present
            float metalness = s.generic0.y;
            float rimFresnel = s.generic0.z;
            float reflFresnel = s.generic0.w;
        #else
            float metalness = 0.0;
            float rimFresnel = pow( saturate(1.0 - dot(s.normal,s.vertexEye)), 5.0 );
            float reflFresnel = 0.05 + 0.95*rimFresnel;
        #endif
        #ifdef REFLECTIVITY_DOTA_EXPORT
            reflFresnel = 0.08; //bottom of the dota fresnel curve
        #endif
        //dota mask 2
        vec4 mask2;
        mask2.x = dot( textureWithSampler( tDotaSpecMask, sDotaSampler, s.vertexTexCoord ), uDotaSpecMaskChannel );
        mask2.y = dot( textureWithSampler( tDotaRimMask, sDotaSampler, s.vertexTexCoord ), uDotaRimMaskChannel );
        mask2.z = dot( textureWithSampler( tDotaTintMask, sDotaSampler, s.vertexTexCoord ), uDotaTintMaskChannel );
        mask2.w = dot( textureWithSampler( tDotaExpMask, sDotaSampler, s.vertexTexCoord ), uDotaExpMaskChannel );

        //reflectivity
        s.reflectivity = mix( s.albedo.xyz, vec3(1.0,1.0,1.0), mask2.b ); //dota docs have this backwards -jdr & drew
        s.reflectivity *= uDota2SpecularScale;
        s.reflectivity *= mask2.r * max( reflFresnel, metalness );
        s.fresnel = vec3(0.0,0.0,0.0);
        //convert phong exponent to marmoset gloss
        float specExp = mask2.a * uDotaSpecularExponent;
        s.gloss = exp2( -10.0 * rsqrt(4.0*specExp + 0.001) );

        //rim lighting
        float rim = saturate( rimFresnel * mask2.g * s.normal.y );
        rim = rim - rim*metalness;
        s.specularLight += rim * uDota2RimLightScale;
    }

Metalness and adv.Metalness

    void    ReflectivityMetalness( inout FragmentState s )
    {
        float m = dot( texture2D( tMetalnessMap, s.vertexTexCoord ), uMetalnessSwizzle );
        m = uMetalnessScaleBias.x * m + uMetalnessScaleBias.y;

        float spec = 0.04;
        #ifdef METALNESS_ADVANCED
            spec = dot( texture2D( tSpecMap, s.vertexTexCoord ), uSpecSwizzle );
            spec = (uSpecCurveAdjust > 0) ? (spec*spec) : spec;
            spec = uSpecScaleBias.x * spec + uSpecScaleBias.y;
        #endif

        s.reflectivity = mix( vec3(spec,spec,spec), s.albedo.xyz, m );
        s.albedo.xyz = s.albedo.xyz - m * s.albedo.xyz;
        s.fresnel = vec3( 1.0, 1.0, 1.0 );
    }

RefractiveIndex

    void    ReflectivityRefractiveIndex( inout FragmentState s )
    {
        //IOR of the scene medium
        vec3 n1 = vec3( uReflectivityMediumIndex, uReflectivityMediumIndex, uReflectivityMediumIndex );
        //IOR of the surface
        vec3 mask = texture2D( tReflectivityRefractiveIndex, s.vertexTexCoord ).xyz;
        vec3 n2 = mix( vec3(1.0,1.0,1.0), uReflectivitySurfaceIndex, mask );
        vec3 k2 = mix( vec3(0.0,0.0,0.0), uReflectivitySurfaceExtinction, mask );
        //shout out to AJ Fresnel
        s.reflectivity = ((n1-n2)*(n1-n2) + k2*k2) / ((n1+n2)*(n1+n2) + k2*k2);
        s.fresnel = vec3(1.0,1.0,1.0);
        //energy conservation plz
        s.albedo.xyz -= s.albedo.xyz*s.reflectivity;
    }

SpecularMap

    void    ReflectivitySpecularMap( inout FragmentState s )
    {
        vec4 t = texture2D( tSpecularMap, s.vertexTexCoord );
        float swz = dot( t, uSpecularSwizzle );
        s.reflectivity = (uSpecularSwizzle.x < 0.0) ? t.rgb : vec3(swz,swz,swz);
        s.reflectivity *= uSpecularColor;

        s.albedo.xyz = s.albedo.xyz - s.albedo.xyz * uSpecularConserve * s.reflectivity;
        s.fresnel = uSpecularFresnel;
    }

Reflection

Aniso


    void    ReflectionAniso( inout FragmentState s )
    {
        vec3 tangent;
        //Direction
        tangent = texture2D( tAnisoDirectionMap, s.vertexTexCoord ).xyz;
        tangent = uAnisoDirectionMapScale*tangent + uAnisoDirectionMapBias;
        //zero if a tangent map is present
        tangent += uAnisoDirectionConst;
        tangent.xy = tangent.yx;
        tangent.x = -tangent.x;
        //swizzle tangent and binormal directions
        //NOTE: swizzle order is flipped for the viewer
        tangent.xy = mix(tangent.xy, tangent.yx, uAnisoDirectionMapSwizzle);

        s.generic3.rgb = normalize( tangent );

        //OPT: technically tangent could be vec2 and uAnisoSpread is a constant in Toolbag. It could be a map in the future.
    }

GGX

    void    ReflectionGGX( inout FragmentState s )
    {
        //scale reflectivity a bit to roughly match ggx occlusion terms
        s.reflectivity *= 0.4 + 0.6*saturate( s.gloss/0.5 );
        //emperical s-curve to match phong as well as we can
        float left = 0.87, right = 1.0 - left;
        if( s.gloss <= left )
        { s.gloss = s.gloss*s.gloss / left; }
        else
        { s.gloss = sqrt( (s.gloss-left)/right )*right + left; }
    }

Mirror

    void    ReflectionMirror( inout FragmentState s )
    {
        s.gloss = 1.0; //convert gloss value for export
    }

BlinnPhong


    void    BlinnPhongLight( inout FragmentState s, LightParams l )
    {
        //determine specular exponent from gloss map & settings
        float gloss = min( s.gloss, 0.995 );
        #ifdef SPECULAR_SECONDARY
            gloss = saturate( gloss * uPhongSecondaryGloss );
        #endif
        float specExp = -10.0 / log2( gloss*0.968 + 0.03 );
        specExp *= specExp;
        //light params
        float phongNormalize = (specExp + 4.0)/(8.0*3.141592);
        adjustAreaLightSpecular( l, reflect( -s.vertexEye, s.normal ), phongNormalize );

        //blinn-phong term
        vec3 H = normalize( s.vertexEye + l.direction );
        float phong = phongNormalize * pow( saturate( dot(H, s.normal) ), specExp );

        //horizon occlusion
        float horizon = 1.0 - saturate( dot( l.direction, s.normal ) );
        horizon *= horizon; horizon *= horizon;
        phong = phong - phong*horizon;
        //spec color
        vec3 spec = (l.shadow.rgb * l.attenuation) *
                    saturate( dot( l.direction, s.normal ) ) *
                    l.color;
        //fresnel
        float glossAdjust = gloss*gloss;
        vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
        #ifdef SPECULAR_SECONDARY
            reflectivity *= uPhongSecondaryIntensity;
            fresn = uPhongSecondaryFresnel;
        #endif
        spec *= fresnel(    dot( s.vertexEye, s.normal ),
                            reflectivity,
                            fresn * glossAdjust );
        //add it on
        s.specularLight += spec * phong;
    }

ReflectionSecondary

    void    ReflectionAnisotropicLight( inout FragmentState s, LightParams l )
    {
        #ifdef SPECULAR_SECONDARY
            float gloss = saturate( s.gloss * uAnisoSecondaryGloss );
        #else 
            float gloss = s.gloss;
        #endif
        adjustAreaLightSpecular( l, reflect( -s.vertexEye, s.normal ), 1.0-gloss );
        //gloss
        //determine specular exponent from gloss map & settings
        float specExp = -10.0 / log2( gloss*0.968 + 0.03 );
        specExp *= specExp;
        float anisoExp = mix( specExp, 16.0, uAnisoSpread );
        //blinn-phong
        vec3 h = normalize( s.vertexEye + l.direction );

        //anisotropic projection
        //h is projected onto the B-plane (shared by strand dir and N) to max out the dot-product along the B axis.
        vec3 N = s.normal;  
        vec3 T, B; SampleAnisoTangent( s, T, B );

        #ifdef SPECULAR_SECONDARY
            N = normalize(N + T * uAnisoSecondaryShift * 0.5);
        #endif

        //TODO: sqrt in c++, square it everywhere else
        h = normalize(h - B*dot(h,B) * sqrt(uAnisoSpread));
        float HdotN = saturate( dot( h, N ) );
        float illum = pow( HdotN, specExp );
        illum *= (specExp + 4.0)/(8.0*3.141592);
        illum *= 1.0 - uAnisoSpread * 0.5;

        //horizon
        float horizon = 1.0 - saturate( dot( l.direction, N ) );
        horizon *= horizon; horizon *= horizon;
        illum = illum - illum*horizon;
        //spec color
        vec3 spec = (l.attenuation * illum) * l.color;
        //fresnel
        float glossAdjust = gloss*gloss;
        vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
        #ifdef SPECULAR_SECONDARY
            reflectivity *= uAnisoSecondaryColor;
            fresn = uAnisoSecondaryFresnel;
        #endif
        spec *= fresnel(    dot( s.vertexEye, s.normal ),
                            reflectivity,
                            fresn * glossAdjust );
        #ifdef SPECULAR_SECONDARY
            spec *= uAnisoSecondaryColor;
        #endif
        //add it on
        s.specularLight += spec * l.shadow.rgb;
    }

ggx

    void    ReflectionGGXLight( inout FragmentState s, LightParams l )
    {
        //roughness
        float roughness = 1.0 - s.gloss;
        #ifdef SPECULAR_SECONDARY
            roughness = saturate(roughness - roughness*uGGXSecondaryGloss);
        #endif
        float a = max( roughness * roughness, 2e-3 );
        float a2 = a * a;
        //light params
        adjustAreaLightSpecular( l, reflect( -s.vertexEye, s.normal ), rcp(3.141592 * a2) );
        //various dot products
        vec3 H = normalize(l.direction + s.vertexEye);
        float NdotH = saturate(dot(s.normal,H));
        float VdotN = saturate(dot(s.vertexEye,s.normal));
        float LdotN = saturate(dot(l.direction,s.normal));
        float VdotH = saturate(dot(s.vertexEye,H));

        //horizon
        float atten = l.attenuation;
    float horizon = 1.0 - LdotN;
    horizon *= horizon; horizon *= horizon;
    atten = atten - atten * horizon;

        //incident light
        vec3 spec = l.color * l.shadow.rgb * (atten * LdotN);

        //microfacet distribution
        float d = ( NdotH * a2 - NdotH ) * NdotH + 1.0;
             d *= d;
        float D = a2 / (3.141593 * d);
        //geometric / visibility
        float k = a * 0.5;
        float G_SmithL = LdotN * (1.0 - k) + k;
        float G_SmithV = VdotN * (1.0 - k) + k;
        float G = 0.25 / ( G_SmithL * G_SmithV );

        //fresnel
        vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
        #ifdef SPECULAR_SECONDARY
            reflectivity *= uGGXSecondaryIntensity;
            fresn = uGGXSecondaryFresnel;
        #endif
        vec3 F = reflectivity + (fresn - fresn*reflectivity) * exp2( (-5.55473 * VdotH - 6.98316) * VdotH );

    //final
        s.specularLight += (D * G) * (F * spec);
    }

Mirror

    void    MirrorLight( inout FragmentState s, LightParams l )
    {
        //boolean ray trace against light shape
        vec3 r = reflect( -s.vertexEye, s.normal );
        bool hit = adjustAreaLightSpecular( l, r, 1.0 );
        hit = hit && dot(r,l.direction) > 0.0;
        vec3 spec = (hit ? l.attenuation : 0.0) * l.color;
        //fresnel
        vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
        #ifdef SPECULAR_SECONDARY
            reflectivity *= uMirrorSecondaryIntensity;
            fresn = uMirrorSecondaryFresnel;
        #endif
        spec *= fresnel( dot( s.vertexEye, s.normal ),
                         reflectivity,
                         fresn );
        //add it on
        s.specularLight += spec * l.shadow.rgb;
    }


BlinnPhong


    void    BlinnPhongLight( inout FragmentState s, LightParams l )
    {
        //determine specular exponent from gloss map & settings
        float gloss = min( s.gloss, 0.995 );
        #ifdef SPECULAR_SECONDARY
            gloss = saturate( gloss * uPhongSecondaryGloss );
        #endif
        float specExp = -10.0 / log2( gloss*0.968 + 0.03 );
        specExp *= specExp;
        //light params
        float phongNormalize = (specExp + 4.0)/(8.0*3.141592);
        adjustAreaLightSpecular( l, reflect( -s.vertexEye, s.normal ), phongNormalize );

        //blinn-phong term
        vec3 H = normalize( s.vertexEye + l.direction );
        float phong = phongNormalize * pow( saturate( dot(H, s.normal) ), specExp );

        //horizon occlusion
        float horizon = 1.0 - saturate( dot( l.direction, s.normal ) );
        horizon *= horizon; horizon *= horizon;
        phong = phong - phong*horizon;
        //spec color
        vec3 spec = (l.shadow.rgb * l.attenuation) *
                    saturate( dot( l.direction, s.normal ) ) *
                    l.color;
        //fresnel
        float glossAdjust = gloss*gloss;
        vec3 reflectivity = s.reflectivity, fresn = s.fresnel;
        #ifdef SPECULAR_SECONDARY
            reflectivity *= uPhongSecondaryIntensity;
            fresn = uPhongSecondaryFresnel;
        #endif
        spec *= fresnel(    dot( s.vertexEye, s.normal ),
                            reflectivity,
                            fresn * glossAdjust );
        //add it on
        s.specularLight += spec * phong;
    }

OcclusionMap

    void    OcclusionMap( inout FragmentState s )
    {
        float diff = 1.0, spec = 1.0;
        #ifdef AMBIENT_OCCLUSION
            vec2 tc = mix( s.vertexTexCoord, s.vertexTexCoordSecondary, uAmbientOcclusionUseSecondaryUV );
            float ao = dot( texture2D( tAmbientOcclusionTexture, tc ), uAmbientOcclusionSwizzle );
            ao *= dot( s.vertexColor, uAmbientOcclusionVertexSwizzle ) + uAmbientOcclusionVertexEnable;
            ao = ao * uAmbientOcclusionStrength.x + uAmbientOcclusionStrength.y;
            #ifdef OCCLUSION_EXPORT
                //output ao instead of applying it
                s.output2.a = sqrt(ao);
            #else
                diff = ao;
            #endif
        #endif

        //cavity mapping
        float cav = dot( texture2D( tCavityTexture, s.vertexTexCoord ), uCavitySwizzle );
        diff *= cav * uCavityStrength.x + uCavityStrength.y;
        spec *= cav * uCavityStrength.z + uCavityStrength.w;
        #ifdef OCCLUSION_EXPORT
            //export; apply cavity to surface instead
            s.albedo.xyz *= diff;
            s.reflectivity *= spec;
        #else
            s.diffuseLight *= diff;
            s.specularLight *= spec;
        #endif
    }

Emissive

ShadowCatcherEmissive

    void    DiffusionShadowCatcherEmissive( inout FragmentState s )
    {
        //shadow ratio
        float eps = 1.0e-4;
        float ao = sampleOcclusionMask( s.screenTexCoord );
        s.diffuseLight = (s.diffuseLight * ao + eps) / (s.generic0.rgb + eps);
        //edge fade
        vec2 fadeCoords = s.vertexTexCoord * 2 - 1;
        float edgeFade = saturate( dot(fadeCoords,fadeCoords) ) * uShadowCatcherParams.z;
        s.diffuseLight = mix( s.diffuseLight, vec3(1.0,1.0,1.0), edgeFade );
        //alpha fade
        s.diffuseLight = mix( vec3(1.0,1.0,1.0), s.diffuseLight, uShadowCatcherParams.y * dot( texture2D( tShadowCatcherAlphaMap, s.vertexTexCoord ), uShadowCatcherAlphaMapSwizzle ) );
    }

FluorescentImage

    void    EmissiveFluorescentImage( inout FragmentState s )
    {
        float e = uFluorescentSphere[0];
        e += uFluorescentSphere[1] * s.normal.y;
        e += uFluorescentSphere[2] * s.normal.z;
        e += uFluorescentSphere[3] * s.normal.x;
        vec3 swz = s.normal.yyz * s.normal.xzx;
        e += uFluorescentSphere[4] * swz.x;
        e += uFluorescentSphere[5] * swz.y;
        e += uFluorescentSphere[7] * swz.z;
        vec3 sqr = s.normal * s.normal;
        e += uFluorescentSphere[6] * ( 3.0*sqr.z - 1.0 );
        e += uFluorescentSphere[8] * ( sqr.x - sqr.y );
        e *= sampleOcclusionMask( s.screenTexCoord );
        s.emissiveLight += uFluorescentColor * texture2D(uFluorescentMap,s.vertexTexCoord).xyz * e;
    }

Heat

    void    EmssiveHeat( inout FragmentState s )
    {
        float temp = mix( uEmissiveHeatRange.x, uEmissiveHeatRange.y, texture2D( tEmissiveHeatMap, s.vertexTexCoord ).x );
        float mireds = 1000.0 / temp; //actually mireds divided by 1000
        float intensity = saturate( temp/ 10000.0 );
        intensity *= intensity;
        intensity *= uEmissiveHeatRange.z;
        s.emissiveLight += intensity * texture2D( tEmissiveHeatSpectrum, vec2(mireds,0.0) ).xyz;
    }

    void    EmssiveMap( inout FragmentState s )
    {
        vec2 tc = mix( s.vertexTexCoord, s.vertexTexCoordSecondary, uEmissionUseSecondaryUV );
        vec3 emissiveMap = texture2D( tEmissiveMap, tc ).xyz;
        s.emissiveLight += uEmission + emissiveMap * uEmissiveColor;
    }

Transparency

    void    TransparencyAlphaTest( inout FragmentState s )
    {
        AlphaBase(s);
        HINT_FLATTEN
        if( s.albedo.a < uAlphaTestValue )
        { discard; }
    }
    void    TransparencyBlend( inout FragmentState s )
    {
        AlphaBase(s);
        float diffuseScale = uDiffuseScale * s.albedo.a;
        s.diffuseLight *= diffuseScale;
        s.albedo.xyz *= diffuseScale;
        s.specularLight *= s.albedo.a;
        s.reflectivity.xyz *= s.albedo.a;
        s.emissiveLight *= s.albedo.a;
    }
    void    TransparencyDither( inout FragmentState s )
    {
        AlphaBase(s);
        vec2 tc = s.screenTexCoord * uAlphaDitherScaleBias.xy + uAlphaDitherScaleBias.zw;
        tc += floor( s.vertexTexCoord * uLayerFloorScale.x ) * uLayerFloorScale.y;
        float noise = texture2D( tDitherPattern, tc ).x;
        HINT_FLATTEN
        if( s.albedo.a < noise )
        { discard; }
    }